﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using Db8.Data;

namespace Db8.DataAccess
{
    public partial class Db8Context : DbContext
    {
        public List<Claim> ListClaims(int skip, int count, bool OnlyWhereHasCounterClaims, bool OnlyIfNotCounterClaim)
        {
            var claims = Claims
                .Include("ClaimKeywords")
                .Include("ClaimKeywords.Keyword")
                .Include("CounterClaims")
                .Include("CounterClaims.Claim")
                //.Include("CounterClaims.Claim.ClaimKeywords")
                //.Include("CounterClaims.Claim.ClaimKeywords.Keyword")
                .AsQueryable();
            
            // filter
            if (OnlyWhereHasCounterClaims)
                claims = claims.Where(c => c.CounterClaims.Any());
            if (OnlyIfNotCounterClaim)
                claims = claims.Where(c => !c.CounteredClaims.Any());

            // order
            claims = claims.OrderByDescending(c => c.ModifiedDt);

            // return requested window
            return claims.Skip(skip).Take(count).ToList();
        }

        public List<Claim> ListClaims(string keyword, int skip, int count, bool OnlyWhereHasCounterClaims, bool OnlyIfNotCounterClaim)
        {
            var claims = Claims
                .Include("ClaimKeywords")
                .Include("ClaimKeywords.Keyword")
                .Include("CounterClaims")
                .Include("CounterClaims.Claim")
                //.Include("CounterClaims.Claim.ClaimKeywords")
                //.Include("CounterClaims.Claim.ClaimKeywords.Keyword")
                .AsQueryable();

            // filter
            if (OnlyWhereHasCounterClaims)
                claims = claims.Where(c => c.CounterClaims.Any());
            if (OnlyIfNotCounterClaim)
                claims = claims.Where(c => !c.CounteredClaims.Any());
            claims = claims.Where(c => c.ClaimKeywords.Any(k => k.Keyword.Value == keyword));

            var deepClaims = Claims
                .Include("ClaimKeywords")
                .Include("ClaimKeywords.Keyword")
                .Include("CounteredClaims")
                .Include("CounteredClaims.TargetClaim")
                .AsQueryable();
            deepClaims = deepClaims.Where(c => c.CounteredClaims.Any());
            deepClaims = deepClaims.Where(c => c.ClaimKeywords.Any(k => k.Keyword.Value == keyword));
            var deepClaimCounteredClaims = deepClaims.SelectMany(deep => deep.CounteredClaims.Select(cc => cc.TargetClaim));


            //var test0 = deepClaims.Select(c => c.Statement).ToList();
            //var test1 = claims.Select(c => c.Statement).ToList();
            //var test2 = deepClaimCounteredClaims.Select(c => c.Statement).ToList();

            claims = claims.Union(deepClaimCounteredClaims);

            // order
            claims = claims.OrderByDescending(c => c.ModifiedDt);

            // return requested window
            return claims.Skip(skip).Take(count).ToList();
        }


        //void SQL()
        //{

        //    string sql = "select * from claim where statement = ?filter limit {skip}, {take}";


        //    //MySqlCommand command = new MySqlCommand();
        //    //command.CommandText = "SELECT * FROM Product WHERE Product.ID=?PROD_ID";
        //    //command.Parameters.Add(new MySqlParameter("?PROD_ID", 100));
        //}

        class ClaimMatch
        {
            public int Score { get; set; }
            public Claim Claim { get; set; }
        }

        public List<Claim> SearchClaims(string search, int dblimit, bool OnlyWhereHasCounterClaims, bool OnlyIfNotCounterClaim)
        {
            // TODO: either exclude keywords from counter-claims or...?

            var claims = Claims
                .Include("ClaimKeywords")
                .Include("ClaimKeywords.Keyword")
                .Include("CounterClaims")
                .Include("CounterClaims.Claim")
                //.Include("CounterClaims.Claim.ClaimKeywords")
                //.Include("CounterClaims.Claim.ClaimKeywords.Keyword")
                .AsQueryable();
            if (OnlyWhereHasCounterClaims)
                claims = claims.Where(c => c.CounterClaims.Any());
            if (OnlyIfNotCounterClaim)
                claims = claims.Where(c => !c.CounteredClaims.Any());


            var tokens = search.Tokenize();
            if (tokens.Count > 0)
            {
                claims = claims.Where(c =>
                    c.ClaimKeywords.Any(k => tokens.Contains(k.Keyword.Value))
                    || tokens.Any(tk => c.ClaimKeywords.Any(k => k.Keyword.Value.Contains(tk)))
                    || tokens.Any(tk => c.Statement.Contains(tk))
                    || tokens.Any(tk => c.Abstract.Contains(tk))
                    );

                //|| tokens.Any(tk => c.CounterClaims.Any(cc => cc.Claim.ClaimKeywords.Any(ck => ck.Keyword.Value.Contains(tk))))
                //|| c.CounterClaims.Any(cc => cc.Claim.ClaimKeywords.Any(ck => tokens.Contains(ck.Keyword.Value)))

                // constructs not supported by MySQL provider:
                //|| c.CounterClaims.Any(cc => tokens.Any(tk => cc.Claim.Statement.Contains(tk))
                //                          || tokens.Any(tk => cc.Claim.Abstract.Contains(tk))));
                //|| 
                //c.CounterClaims.Any(cc => cc.Claim.ClaimKeywords.Where(k => tokens.Contains(k.Keyword.Value)).Any()
                //                          || tokens.Any(tk => cc.Claim.Statement.Contains(tk))
                //                          || tokens.Any(tk => cc.Claim.Abstract.Contains(tk))));
            }

            // limit traffic with db... this should be on the other side of scoring/sorting, but for now it will do
            if (dblimit > 0)
                claims = claims.Take(dblimit);

            // score in memory:
            var rankedList = claims.ToList().Select(c => new ClaimMatch { Score = 3, Claim = c }).ToList();
            if (rankedList.Count < dblimit && tokens.Count > 0)
            {
                // look deeper
                var deepClaims = Claims
                    .Include("ClaimKeywords")
                    .Include("ClaimKeywords.Keyword")
                    .Include("CounteredClaims")
                    .Include("CounteredClaims.TargetClaim")
                    .AsQueryable();
                deepClaims = deepClaims.Where(c => c.CounteredClaims.Any());
                deepClaims = deepClaims.Where(c =>
                    c.ClaimKeywords.Any(k => tokens.Contains(k.Keyword.Value))
                    || tokens.Any(tk => c.ClaimKeywords.Any(k => k.Keyword.Value.Contains(tk)))
                    || tokens.Any(tk => c.Statement.Contains(tk))
                    || tokens.Any(tk => c.Abstract.Contains(tk))
                    );
                deepClaims = deepClaims.Take(2 * dblimit);
                var deepList = deepClaims.ToList();
                List<ClaimMatch> deepRanked = new List<ClaimMatch>();
                foreach (var deepMatch in deepList)
                {
                    int score = 1;
                    if (deepMatch.ClaimKeywords.Any(k => tokens.Contains(k.Keyword.Value)))
                        score = 3;
                    deepRanked.AddRange(deepMatch.CounteredClaims.Select(cc => new ClaimMatch() { Score = score, Claim = cc.TargetClaim }));
                }

                var deepCounteredKeywords = Claims
                    .Include("ClaimKeywords")
                    .Include("ClaimKeywords.Keyword")
                    .WhereIn(c => c.ClaimId, deepRanked.Select(m => m.Claim.ClaimId));
                //foreach (var dck in deepCounteredKeywords)
                //{
                //    var match = deepRanked.FirstOrDefault(dr => dr.Claim.ClaimId == dck.ClaimId);
                //    if (match != null)
                //        match.Claim.ClaimKeywords = dck.ClaimKeywords;
                //}


                // merge 
                foreach (ClaimMatch match in deepRanked.OrderByDescending(dm => dm.Score))
                {
                    var li = rankedList.FirstOrDefault(m => m.Claim.ClaimId == match.Claim.ClaimId);
                    if (li == null)
                    {
                        rankedList.Add(match);
                    }
                    else
                    {
                        if (li.Score >= 9)
                            li.Score += 1;
                        else
                            li.Score += match.Score;
                    }
                }
            }


            rankedList.ForEach(li =>
            {

                // 3 for each keyword=token match
                li.Score += 9 * li.Claim.ClaimKeywords.Where(k => tokens.Contains(k.Keyword.Value)).Count();

                // 1 for each token in the claim statement
                li.Score += 3 * tokens.Count(tk => li.Claim.Statement.Contains(tk));

                //// 3 for each keyword=token match in a counterclaim
                //li.Score += 3 * li.Claim.CounterClaims.Sum(cc => cc.Claim.ClaimKeywords.Where(k => tokens.Contains(k.Keyword.Value)).Count());

                //// 1 for each token in a counterclaim statement
                //li.Score += 1 * li.Claim.CounterClaims.Sum(cc => tokens.Count(tk => cc.Claim.Statement.Contains(tk)));

                // 6 for an exact phrase match in the claim statement
                if (tokens.Count > 1 && li.Claim.Statement.Contains(search))
                    li.Score += 6;

                // 3 for an exact phrase match in the abstract statement
                if (tokens.Count > 1 && li.Claim.Abstract.Contains(search))
                    li.Score += 3;
            });

            List<Claim> results = rankedList.OrderByDescending(li => li.Score).ThenByDescending(li => li.Claim.ModifiedDt).Select(li => li.Claim).ToList();
            return results;
        }

        public Claim GetClaimById(int id)
        {
            Claim claim = Claims
                .Include("ClaimKeywords")
                .Include("CreatedByUser")
                .Include("ModifiedByUser")
                .Include("ClaimKeywords.Keyword")
                .Include("CounterClaims")
                .Include("CounterClaims.Claim")
                .Where(c => c.ClaimId == id).FirstOrDefault();
            if (claim == null)
            {
                return null;
            }

            return claim;
        }
    }
}
